今天寫個時事題,我們來查詢立委議案提案
本日重點
為節省篇幅,本示例僅列出關鍵程式碼,畫面編排、按鍵 onclick、DataLoader、Grid顯示...等使用方法,請參考本系列其他文章。
資料來源 : 立法院開放資料服務平台
立法院提供三種資料模式,分別是 csv、txt、xls (雖然立法院開放資料服務平台
上有JSON選項,但並未提供內容)
從網站資訊得知此api提供欄位如下 :
| name|委員姓名||ename|委員英文姓名|
|-|-|-|-|
| sex|性別|| party|黨籍|
| partyGroup|黨團|| areaName|選區名稱|
| committee|委員會|| onboardDate|到職日(西元年)|
| degree|學歷|| tel|電話|
| experience|經歷|| fax|傳真|
| addr|通訊處|| picUrl|照片位址|
| leaveFlag|是否離職|| leaveDate|離職日期(西元年)|
| leaveReason|離職原因|
Legislator
,請開新檔 V05__CreateLegislator.sql
create TABLE Legislator(
id bigint auto_increment PRIMARY KEY,
term VARCHAR(3),
name VARCHAR(50) NOT NULL,
sex VARCHAR(10) NOT NULL,
party VARCHAR(100),
partyGroup VARCHAR(100),
areaName VARCHAR(100),
committee VARCHAR(500),
degree VARCHAR(200),
tel VARCHAR(200),
experience VARCHAR(1000),
addr VARCHAR(500),
picUrl VARCHAR(100),
leaveFlag VARCHAR(10),
leaveDate VARCHAR(20),
leaveReason VARCHAR(1000)
);
KEntity
類data class Legislator(
override var id: Long? = null,
var term: String? = null,
var name: String? = null,
var sex: String? = null,
var party: String? = null,
var partyGroup: String? = null,
var areaName: String? = null,
var committee: String? = null,
var degree: String? = null,
var tel: String? = null,
var experience: String? = null,
var addr: String? = null,
var picUrl: String? = null,
var leaveFlag: String? = null,
var leaveDate: String? = null,
var leaveReason: String? = null
):KEntity<Long> ,Serializable{
companion object: Dao<Legislator, Long>(Legislator::class.java)
}
CSV 轉成 Bean 的資料轉換使用 OpenCSV 套件,請開啟 build.gradle.kts
,導入opencsv 套件
implementation("com.opencsv:opencsv:5.5.2")
將下載寫在download()
方法,轉檔後儲存到資料表Legislator
fun download(){
URL("https://data.ly.gov.tw/odw/usageFile.action?id=9&type=CSV&fname=9_CSV.csv")
.readText().apply {
CsvToBeanBuilder<Legislator>(StringReader(this))
.withType(Legislator::class.java)
.withSeparator(',')
.withIgnoreLeadingWhiteSpace(true)
.withIgnoreEmptyLine(true)
.build()
.parse().forEach {
it.save()
}
}
}
第2,3行,從立法院開放資料服務平台
取得資料
第4行,使用CsvToBeanBuilder()
方法進行轉換,參數型態為Reader
,所以將下載回來的字串進行轉換
第5行,parser型態
第6行,分隔符號
第7行,忽略前置空白
第8行,忽略空白行
第11行,因Legislator
為KEntity
類,直接使用save()
方法儲存到資料庫。
設定 Grid 資料來源為 EntityDataLoader
,首次執行時因資料表 Legislator
尚無任何資料,grid為空白。
grid = grid<Legislator> {
setDataLoader(Legislator.dataLoader)
addColumnFor(Legislator::name).setHeader("委員姓名")
(略)
}
下載立委資料並儲存後,更新Grid
download()
grid.dataProvider.refreshAll()
點選grid row 展開detail,希望detail顯示相片、所屬委員會、服務處地址等資訊。
所屬委員會欄位committee
內容如下:
第10屆第1會期:經濟委員會;第10屆第2會期:經濟委員會;第10屆第3會期:經濟委員會;第10屆第3會期:紀律委員會;第10屆第4會期:經濟委員會;
以;
分隔每筆資料,先將資料整理一下再放入List,最後以ListBox
顯示
val committees = mutableListOf<String>()
data.committee?.let {
it.split(";").forEach { committees.add(it) }
}
val listBox = ListBox<String>()
listBox.setWidth("35%")
listBox.setItems(committees)
完整內容如下
setSelectionMode(Grid.SelectionMode.NONE)
val renderer = ComponentRenderer<HorizontalLayout, Legislator> { data: Legislator ->
val horizontalLayout = HorizontalLayout()
val image = Image()
with(image){
isExpand = false
width = "10%"
setMaxWidth(200F, Unit.PIXELS)
setMaxHeight(200F, Unit.PIXELS)
if (!data.picUrl.isNullOrEmpty())
src = data.picUrl
}
horizontalLayout.add(image)
val committees = mutableListOf<String>()
data.committee?.let {
it.split(";").forEach { committees.add(it) }
}
val listBox = ListBox<String>()
listBox.setWidth("35%")
listBox.setItems(committees)
horizontalLayout.add(listBox)
val listBox_addr = ListBox<String>()
val addrs = mutableListOf<String>()
data.addr?.let {
it.split(";").forEach { addrs.add(it) }
}
listBox_addr.setWidth("50%")
listBox_addr.setItems(addrs)
horizontalLayout.add(listBox_addr)
horizontalLayout
}
setItemDetailsRenderer(renderer)
上述程式中,ListBox 不同的資料來源,皆須將資料切分後,使用ListBox顯示,程式碼看起來不僅不親民、兀長,且未能 reuse。複習一下先前曾學過的 Custom Component,Refactor 一下,將 ListBox 改為 Component。
開新檔ListComponent.kt
class ListComponent(data: String, split: String, title: String) : KComposite() {
val list = mutableListOf<String>()
private val root = ui {
data.split(split).forEach {
if (!it.isNullOrEmpty()) list.add(it.trim())
}
verticalLayout {
label(title)
listBox<String> { setItems(list) }
}
}
}
fun HasComponents.listComponent(
data: String,
split: String,
title: String,
block: ListComponent.() -> kotlin.Unit = {}
) = init(ListComponent(data, split, title), block)
傳入三個參數,data、split、title
上述兀長的資料轉換、加入ListBox()等程式內容,只要改成這樣就行了,是不是簡單易讀多了。
horizontalLayout.add(
ListComponent(data.committee!!, ";", "委員會" )
)
horizontalLayout.add(
ListComponent(data.addr!!, ";", "服務處")
)
提供欄位有
term | 屆別 | sessionPeriod | 會期 | sessionTimes | 會次 |
---|---|---|---|---|---|
meetingTimes | 臨時會會次 | billNo | 議案編號 | billName | 提案名稱 |
billOrg | 提案單位/委員 | billProposer | 提案人(委員或黨團) | billCosignatory | 連署人 |
billStatus | 議案狀態 | pdfUrl | 關係文書pdf檔案下載位置 | docUrl | 關係文書doc檔案下載位置 |
selectTerm | 屆別期別篩選條件 |
建立資料表 ProposalOfBills
create TABLE ProposalOfBills(
id bigint auto_increment PRIMARY KEY,
billNo VARCHAR(20),
billName VARCHAR(500),
billOrg VARCHAR(50),
billProposer VARCHAR(200),
billCosignatory VARCHAR(1000),
billStatus VARCHAR(10),
pdfUrl VARCHAR(100),
docUrl VARCHAR(100)
);
新增 KEntity
類 ProposalOfBills
data class ProposalOfBills(
override var id: Long? = null,
var billNo: String? = null,
@field:NotNull
var billName: String? = null,
@field:NotNull
var billOrg: String? = null,
var billProposer: String? = null,
var billCosignatory: String? = null,
var billStatus: String? = null,
var pdfUrl: String? = null,
var docUrl: String? = null
): KEntity<Long>, Serializable{
companion object: Dao<ProposalOfBills, Long>(ProposalOfBills::class.java)
}
下載的部份和下載委員資料大同小異,僅列出和上述不同的部份
.withThrowExceptions(false)
從前述資料可知,提供的資料欄位數遠大於所需,故只儲存部份欄位備用。但是這樣一來,parse 過程會因為欄位對應不上而拋出 Exceptions,為免轉檔因此中斷。所以加上這一行讓parse可繼續進行。
輸入委員姓名後要過濾兩個欄位,提案人(billProposer
)和連署人(billCosignatory
)
filter = textField {
placeholder = "委員姓名"
addValueChangeListener { event ->
var dp: DataLoader<ProposalOfBills> = ProposalOfBills.dataLoader
if (!filter.value.isBlank())
dp = dp.withFilter {
(ProposalOfBills::billProposer contains filter.value) or
(ProposalOfBills::billCosignatory contains filter.value)
}
grid.setDataLoader(dp)
}
valueChangeMode = ValueChangeMode.EAGER
isExpand = true
}
setVerticalComponentAlignment(FlexComponent.Alignment.START, filter)
第4行,取得EntityDataLoader
第5行,若欄位不為空,加過濾條件
第6行,加條件
第7,8行,我們要找的資料是委員姓名在欄位裡任一位置都可以,所以使用 contains
比對
val renderer = ComponentRenderer<HorizontalLayout, ProposalOfBills> { data: ProposalOfBills ->
val horizontalLayout = HorizontalLayout()
horizontalLayout.add(
ListComponent(data.billProposer!!, " ", "提案人(委員或黨團)")
)
horizontalLayout.add(
ListComponent(data.billCosignatory!!, " ", "連署人")
)
horizontalLayout
}
方才寫好的 ListComponent
又派上用場了